/*
Description
This Demo shows how to use an image as background picture in D3D
1. Create a off screen surface by CreateOffscreenPlainSurface
2. Load an image file such as .bmp .jpg to the surface by D3DXLoadSurfaceFromFile
3. Get the back buffer of current render device by GetBackBuffer
4. Copy data from the off screen surface to the back buffer by StretchRect
- This step should be done between BeginScene and EndScene

In this demo we draw a chessboard as background image and also draw a teapot to see the effect

Known issue:
it seems the CPU usage is a little bit higher, what's the root cause?
*/

#include <d3dx9.h> 
#include <DxErr.h>
#include <dxdiag.h>

LPDIRECT3D9             g_pD3D			= NULL ; // Used to create the D3DDevice 
LPDIRECT3DDEVICE9       g_pd3dDevice	= NULL ; // Our rendering device 
ID3DXMesh*              g_pTeapotMesh	= NULL ; // Hold the teapot 
IDirect3DSurface9*		g_pBackBuffer	= NULL ; // Back buffer for current device
IDirect3DSurface9*		g_pSourceSurface= NULL ; // Surface to hold the image

#define SAFE_RELEASE(p) if (p){p->Release() ; p = NULL ;}

// Load an image from file as background image
void PrepareImage(char* imageName)
{
	HRESULT hr ;

	// Create surface
	hr = g_pd3dDevice->CreateOffscreenPlainSurface(512, 
		512,
		D3DFMT_X8R8G8B8,	
		D3DPOOL_DEFAULT, 
		&g_pSourceSurface, 
		NULL) ;
	if (FAILED(hr))
	{
		DXTRACE_ERR_MSGBOX(DXGetErrorString(hr), hr) ;
		return ;
	}

	// Load surface from file
	hr = D3DXLoadSurfaceFromFile(g_pSourceSurface, NULL, NULL, imageName, NULL, D3DX_DEFAULT, NULL, NULL) ;
	if (FAILED(hr))
	{
		DXTRACE_ERR_MSGBOX(DXGetErrorString(hr), hr) ;
		return ;
	}
}

HRESULT InitD3D( HWND hWnd ) 
{ 
	// Create the D3D object, which is needed to create the D3DDevice. 
	if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) 
	{ 
		MessageBoxA(NULL, "Create D3D9 object failed!", "Error", 0) ; 
		return E_FAIL; 
	} 

	D3DPRESENT_PARAMETERS d3dpp;  
	ZeroMemory( &d3dpp, sizeof(d3dpp) ); 

	d3dpp.Windowed = TRUE; // use window mode, not full screen 
	d3dpp.BackBufferCount = 1 ;
	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; 
	d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; 
	d3dpp.EnableAutoDepthStencil = TRUE ;
	d3dpp.AutoDepthStencilFormat = D3DFMT_D16 ;

	// Create device 
	if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, 
		D3DCREATE_SOFTWARE_VERTEXPROCESSING, 
		&d3dpp, &g_pd3dDevice ) ) ) 
	{ 
		MessageBoxA(NULL, "Create D3D9 device failed!", "Error", 0) ; 
		return E_FAIL; 
	} 

	// Create teapot 
	D3DXCreateTeapot(g_pd3dDevice, &g_pTeapotMesh, NULL) ; 

	// Prepare backgound image
	PrepareImage("../Common/Media/chessboard.jpg") ;

	// Get backbuffer
	g_pd3dDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &g_pBackBuffer) ;

	return S_OK; 
} 

VOID Cleanup() 
{ 
	// Release teapot mesh 
	SAFE_RELEASE(g_pTeapotMesh) ;

	// Release D3D 
	SAFE_RELEASE(g_pD3D) ; 

	// Release device 
	SAFE_RELEASE(g_pd3dDevice) ; 

	// Release surface
	SAFE_RELEASE(g_pSourceSurface) ;

	// Release backbuffer
	SAFE_RELEASE(g_pBackBuffer) ;
} 

void SetupMatrix() 
{ 
	// translate model to origin 
	D3DXMATRIX world ; 
	D3DXMatrixTranslation(&world, 0.0f, 0.0f, 0.0f) ; 
	g_pd3dDevice->SetTransform(D3DTS_WORLD, &world) ; 

	// set view 
	D3DXVECTOR3 eyePt(0.0f, 0.0f, -10.0f) ; 
	D3DXVECTOR3 upVec(0.0f, 1.0f, 0.0f) ; 
	D3DXVECTOR3 lookCenter(0.0f, 0.0f, 0.0f) ; 

	D3DXMATRIX view ; 
	D3DXMatrixLookAtLH(&view, &eyePt, &lookCenter, &upVec) ; 
	g_pd3dDevice->SetTransform(D3DTS_VIEW, &view) ; 

	// set projection 
	D3DXMATRIX proj ; 
	D3DXMatrixPerspectiveFovLH(&proj, D3DX_PI / 4, 1.0f, 1.0f, 1000.0f) ; 
	g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &proj) ; 
} 

void SetMaterial() 
{ 
	// Set Red material 
	D3DMATERIAL9 redMaterial ; 
	ZeroMemory(&redMaterial, sizeof(redMaterial)) ; 
	redMaterial.Diffuse = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f) ; 
	redMaterial.Ambient = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f) ; 
	redMaterial.Specular = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f) ; 
	redMaterial.Emissive = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f) ; 
	g_pd3dDevice->SetMaterial(&redMaterial) ; 
} 

void SetupLight() 
{ 
	// Enable lighting 
	g_pd3dDevice->SetRenderState( D3DRS_LIGHTING , TRUE ); 

	// Setup a directional light
	D3DLIGHT9 light; 
	ZeroMemory( &light, sizeof(light) ); 

	light.Type      = D3DLIGHT_DIRECTIONAL; 
	light.Ambient   = D3DXCOLOR(0.8f, 0.8f, 0.8f, 1.0f); 
	light.Diffuse   = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); 
	light.Specular  = D3DXCOLOR(0.2f, 0.2f, 0.2f, 1.0f); 
	light.Direction = D3DXVECTOR3(10.0f, 10.0f, -10.0f); 
	g_pd3dDevice->SetLight(0, &light); 
	g_pd3dDevice->LightEnable(0, true); 

	// What's the following statements do?
	g_pd3dDevice->SetRenderState(D3DRS_NORMALIZENORMALS, true); 
	g_pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, true); 
}; 

void DrawTeapot()
{
	// Enable light
	g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, TRUE) ;

	// Create material
	SetMaterial() ;

	// Create light
	SetupLight() ;

	// Draw teapot
	g_pTeapotMesh->DrawSubset(0) ;
}

VOID Render() 
{ 
	SetupMatrix() ;

	// Clear the back-buffer to a RED color 
	HRESULT hr ;
	hr = g_pd3dDevice->Clear(0, 
		NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 
		D3DCOLOR_XRGB(0,0,0), 
		1.0f, 
		0); 
	if (FAILED(hr))
	{
		DXTRACE_ERR_MSGBOX(DXGetErrorDescription(hr), hr) ;
	}

	// Begin the scene 
	if( SUCCEEDED( g_pd3dDevice->BeginScene() ) ) 
	{
		// Draw background
		g_pd3dDevice->StretchRect(g_pSourceSurface, NULL, g_pBackBuffer, NULL, D3DTEXF_NONE) ;

		// Draw sphere
		DrawTeapot() ;
		
		// End the scene 
		g_pd3dDevice->EndScene(); 
	} 

	// Present the back-buffer contents to the display 
	g_pd3dDevice->Present( NULL, NULL, NULL, NULL ); 
} 

LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) 
{ 
	switch( msg ) 
	{ 
	case WM_KEYDOWN: 
		{ 
			switch( wParam ) 
			{ 
			case VK_ESCAPE: 
				SendMessage( hWnd, WM_CLOSE, 0, 0 ); 
				break ; 
			default: 
				break ; 
			} 
		} 
		break ; 

	case WM_DESTROY: 
		Cleanup(); 
		PostQuitMessage( 0 ); 
		return 0; 
	} 

	return DefWindowProc( hWnd, msg, wParam, lParam ); 
} 

INT WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR szCmdLine, int iCmdShow) 
{ 
	WNDCLASSEX winClass ; 

	winClass.lpszClassName = "Teapot"; 
	winClass.cbSize        = sizeof(WNDCLASSEX); 
	winClass.style         = CS_HREDRAW | CS_VREDRAW; 
	winClass.lpfnWndProc   = MsgProc; 
	winClass.hInstance     = hInstance; 
	winClass.hIcon         = NULL ; 
	winClass.hIconSm       = NULL ; 
	winClass.hCursor       = LoadCursor(NULL, IDC_ARROW) ; 
	winClass.hbrBackground = NULL ; 
	winClass.lpszMenuName  = NULL ; 
	winClass.cbClsExtra    = 0; 
	winClass.cbWndExtra    = 0; 

	RegisterClassEx (&winClass) ;   

	HWND hWnd = CreateWindowEx(NULL,   
		winClass.lpszClassName,	// window class name 
		"Teapot",				// window caption 
		WS_OVERLAPPEDWINDOW,	// window style 
		32,						// initial x position 
		32,						// initial y position 
		512,					// initial window width 
		512,					// initial window height 
		NULL,					// parent window handle 
		NULL,					// window menu handle 
		hInstance,				// program instance handle 
		NULL) ;					// creation parameters 

	// Create window failed 
	if(hWnd == NULL) 
	{ 
		MessageBoxA(hWnd, "Create Window failed!", "Error", 0) ; 
		return -1 ; 
	} 

	// Initialize Direct3D 
	if( SUCCEEDED(InitD3D(hWnd))) 
	{  
		// Show the window 
		ShowWindow( hWnd, SW_SHOWDEFAULT ); 
		UpdateWindow( hWnd ); 

		// Enter the message loop 
		MSG    msg ;  
		ZeroMemory( &msg, sizeof(msg) ); 
		PeekMessage( &msg, NULL, 0U, 0U, PM_NOREMOVE ); 

		// Get last time 
		static DWORD lastTime = timeGetTime(); 

		while (msg.message != WM_QUIT)   
		{ 
			if( PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE) != 0) 
			{ 
				TranslateMessage (&msg) ; 
				DispatchMessage (&msg) ; 
			} 
			else // Render the game if there is no message to process 
			{ 
				// Get current time 
				DWORD currTime  = timeGetTime(); 

				// Calculate time elapsed 
				float timeDelta = (currTime - lastTime)*0.001f; 

				// Render 
				Render() ; 

				// Update last time to current time for next loop 
				lastTime = currTime; 
			} 
		} 
	} 

	UnregisterClass(winClass.lpszClassName, hInstance) ; 
	return 0; 
} 
